/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package stackCalc;

import stackCalc.command.BadArgumentsException;
import stackCalc.command.BadCommandException;
import stackCalc.command.Command;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;
import org.apache.log4j.Category;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;

/**
 *	This is simple stack calculator. <br/>
 *  Functions: <br/>
 *  1) POP - throws out last element of stack without printed it. <br/>
 *	2) PUSH - Have 1 argument. Just push into stack argument. <br/>
 *	3,4,5,6) +,-,*,/ - Trivial. Example (a b) in stack, command "-" means a - b. Also this commands
 *						delete 2 last elements of stack and push result. <br/>
 *  7) DEFINE - Have 2 arguments. First is "name", Second is value. <br/>
 *  8) SQRT - Replace last element of stack square root of it. <br/>
 *  9) PRINT - Get last element of stack and print it. <br/>
 *	10) EXIT - exit calculator. <br/>
 *  11) CLOSE - Close application. <br/>
 * 
 * @author signal
 */
public class Calculator {
	
	Logger log = Logger.getLogger(Calculator.class);
	
	private final Stack stack = new Stack();
	private List<Object> args = new ArrayList();
	private HashMap<String, Command> commands = new HashMap();
	private OutputStream out;
	
	/**
	 * Constructor without arguments.
	 */
	
	private Calculator() {  }
	
	/**
	 * Constructor with arguments.
	 * 
	 * @param fileCalcCommands file with commands to define
	 * @param out from print
	 */
	
	public Calculator(String fileCalcCommands, OutputStream out)
	{
		log.info("Calculator creating.");
		this.out = out;
		Scanner inputCommands;
		String command;
		try {		
			HashMap<String, String> symbolToCommand = new HashMap();
				symbolToCommand.put("+", "Plus");
				symbolToCommand.put("-", "Minus");
				symbolToCommand.put("*", "Multiply");
				symbolToCommand.put("/", "Divide");
			inputCommands = new Scanner(new File(fileCalcCommands));
			Pattern pattern = Pattern.compile("[A-Z]+|[+]|[-]|[*]|[/]");
			log.info("--Creating commands.");
			while(inputCommands.hasNext())
			{
				command = inputCommands.next(pattern);
				if(symbolToCommand.containsKey(command))
					commands.put(command, (Command)(Class.forName("stackCalc.command." + symbolToCommand.get(command) + "Command").newInstance()));
				else 
					commands.put(command, (Command)(Class.forName("stackCalc.command." + command + "Command").newInstance()));
				commands.get(command).setOutput(out);
				log.info("----" + command + " created.");
			}
		} catch(FileNotFoundException fnfe) {
			System.err.println("File not found.");
		} catch(ClassNotFoundException cnfe) {
			System.err.println("Error in creating commands. Can't find class.");
		} catch(InstantiationException ie) {
			System.err.println("Error in creating commands.");
		} catch(IllegalAccessException iae) {
			System.err.println("Error in creating commands.");
		}
	}
	
	/**
	 * 
	 * Function to invoke command from calculator.
	 * 
	 * @param command command to invoke.
	 * @throws BadCommandException
	 * @throws BadArgumentsException 
	 */
	
	private void invoke(String command) throws BadCommandException, BadArgumentsException
	{
		if(commands.containsKey(command))
		{
			String logInfo;
			logInfo = "Invoking " + command + "(";
			for(Object arg : args.subList(1, args.size()))
				logInfo += (String)arg + ",";
			logInfo = logInfo.substring(0, logInfo.length() - 1);
			if(args.size() > 1)
				logInfo += ")";
			log.info(logInfo);
			try {
				commands.get(command).execute(args);
			} catch(BadArgumentsException bae) {
				throw bae;
			}
			log.info("Success.");
		}
		else
		{
			log.info("Fail. Unknown command.");
			throw new BadCommandException("Unknown command.\n");
		}
	}
	
	/**
	 * If you want try calculator, invoke this function.
	 * 
	 * @param in Input stream for commands to execute.
	 */
	
	public void start(InputStream in) throws IOException
	{
		log.info("Start executing.");
		Pattern pattern = Pattern.compile("([^-^*^/^+^a-z^A-Z^0-9]+)");
		Pattern comment = Pattern.compile("#.*");
		Scanner input = new Scanner(in);
		String commandLine;
		String[] command;
		while(input.hasNextLine())
		{
			args.add(stack);
			commandLine = input.nextLine();
			commandLine = commandLine.split(comment.pattern())[0];
			command = commandLine.split(pattern.pattern());
			try {
				if(command[0].equalsIgnoreCase("EXIT"))
				{
					log.info("Exiting.");
					break;
				}
			} catch(IndexOutOfBoundsException ioobe) {
				out.write("Unknown command.\n".getBytes());
			}
			for(int i = 1; i < command.length; i++)
			{   
				if(stack.getVariables().containsKey(command[i]))
					args.add(stack.getVariables().get(command[i]));
				else
					args.add(command[i]);
			}
			try {
				invoke(command[0]);
			} catch(BadCommandException bce) {		
				out.write(bce.getError().getBytes());
			} catch(BadArgumentsException bae) {
				out.write(bae.getError().getBytes());
			} catch(IndexOutOfBoundsException ioobe) {
				out.write("Unknown command.\n".getBytes());
			}
			args.removeAll(args);
		}
		input.close();
	}
	
}







